using System;
using System.Xml;
using System.Diagnostics;
using System.Collections.Generic;

namespace Twintsam.Html
{
    public partial class HtmlReader : XmlReader
    {
        private enum InsertionMode
        {
            BeforeDoctype, // NEW
            BeforeHtml, // NEW
            BeforeHead,
            InHead,
            AfterHead,
            InBody,
            InTable,
            InCaption,
            InColumnGroup,
            InTableBody,
            InRow,
            InCell,
            InSelect,
            AfterBody,
            InFrameset,
            AfterFrameset,
            AfterHtml, // NEW
        }

        private struct Node
        {
            public Node(string name) : this(XmlNodeType.Element, name) { }

            public Node(XmlNodeType nodeType, string name)
            {
                this.nodeType = nodeType;
                this.name = name;
                this.attributes = null;
            }

            public XmlNodeType nodeType;
            public string name;
            public Attribute[] attributes;
        }

        private bool _disposed;
        private ParsingFunction _currentReadingFunction;

        private InsertionMode _insertionMode = InsertionMode.BeforeDoctype;

        private int _depth;
        private int _attrIndex;

        private List<string> _openElements = new List<string>();
        private List<Node> _activeFormattingElements = new List<Node>();
        private Queue<Node> _virtualNodes = new Queue<Node>();

        public override int AttributeCount
        {
            get
            {
                // TODO: handle "virtual nodes" generated by MoveToAttribute
                ICollection<Attribute> attributes;
                if (_virtualNodes.Count > 0) {
                    attributes = _virtualNodes.Peek().attributes;
                } else {
                    attributes = _attributes;
                }
                return attributes == null ? 0 : attributes.Count;
            }
        }

        public override string BaseURI
        {
            get { throw new NotImplementedException(); }
        }

        public override void Close()
        {
            _disposed = true;
            throw new NotImplementedException();
        }

        public override int Depth
        {
            get
            {
                if (InAttributeValue) {
                    return _depth + 2;
                } else if (InAttribute) {
                    return _depth + 1;
                } else {
                    return _depth;
                }
            }
        }

        public override bool EOF
        {
            get { return _virtualNodes.Count == 0 && EndOfStream; }
        }

        public override string GetAttribute(int i)
        {
            // TODO: handle "virtual nodes" generated by MoveToAttribute
            if (_virtualNodes.Count > 0) {
                return _virtualNodes.Peek().attributes[i].value;
            } else {
                return _attributes[i].value;
            }
        }

        public override string GetAttribute(string name, string namespaceURI)
        {
            if (!String.IsNullOrEmpty(namespaceURI)) {
                return null;
            }

            return GetAttribute(name);
        }

        public override string GetAttribute(string name)
        {
            // TODO: handle "virtual nodes" generated by MoveToAttribute
            IEnumerable<Attribute> attributes;
            if (_virtualNodes.Count > 0) {
                attributes = _virtualNodes.Peek().attributes;
            } else {
                attributes = _attributes;
            }
            foreach (Attribute attribute in attributes) {
                if (NameTable.Add(attribute.name) == NameTable.Get(name)) {
                    return attribute.value;
                }
            }
            return null;
        }

        public override bool HasValue
        {
            get {
                switch (NodeType) {
                case XmlNodeType.Attribute:
                case XmlNodeType.CDATA:
                case XmlNodeType.Comment:
                case XmlNodeType.Text:
                case XmlNodeType.SignificantWhitespace:
                case XmlNodeType.Whitespace:
                    return true;
                case XmlNodeType.DocumentType:
                case XmlNodeType.ProcessingInstruction:
                case XmlNodeType.XmlDeclaration:
                    throw new InvalidOperationException();
                default:
                    return false;
                }
            }
        }

        public override bool IsEmptyElement
        {
            get { return Constants.IsVoidElement(LocalName); }
        }

        public override string LocalName
        {
            get
            {
                // TODO: handle "virtual nodes" generated by MoveToAttribute
                if (InAttributeValue) {
                    throw new NotImplementedException();
                } else if (InAttribute) {
                    throw new NotImplementedException();
                } else {
                    if (_virtualNodes.Count > 0) {
                        return _virtualNodes.Peek().name;
                    } else {
                        return _name;
                    }
                }
            }
        }

        private bool InAttribute
        {
            get
            {
                // TODO: InAttribute
                return false;
            }
        }

        private bool InAttributeValue
        {
            get
            {
                // TODO: InAttributeValue
                return false;
            }
        }

        public override bool MoveToElement()
        {
            if (NodeType == XmlNodeType.Attribute) {
                _attrIndex = -1;
                return true;
            } else {
                return false;
            }
        }

        public override void MoveToAttribute(int i)
        {
            if (i < 0 || i >= AttributeCount) {
                throw new ArgumentOutOfRangeException("i");
            }
            MoveToElement();
            _attrIndex = i;
        }

        public override bool MoveToAttribute(string name, string ns)
        {
            if (!String.IsNullOrEmpty(ns)) {
                return false;
            }
            return MoveToAttribute(name);
        }

        public override bool MoveToAttribute(string name)
        {
            // TODO: handle "virtual nodes" generated by MoveToAttribute
            Attribute[] attributes;
            if (_virtualNodes.Count > 0) {
                attributes = _virtualNodes.Peek().attributes;
            } else {
                attributes = _attributes.ToArray();
            }
            int index = Array.FindIndex<Attribute>(attributes, delegate(Attribute attribute) {
                return NameTable.Add(attribute.name) == NameTable.Get(name);
            });
            if (index >= 0) {
                MoveToAttribute(index);
                return true;
            } else {
                return false;
            }
        }

        public override bool MoveToFirstAttribute()
        {
            if (AttributeCount > 0) {
                MoveToAttribute(0);
                return true;
            } else {
                return false;
            }
        }

        public override bool MoveToNextAttribute()
        {
            if (_attrIndex < AttributeCount - 1) {
                return false;
            } else {
                MoveToAttribute(_attrIndex + 1);
                return true;
            }
        }

        public override XmlNodeType NodeType
        {
            get
            {
                if (InAttributeValue) {
                    return XmlNodeType.Text;
                } else if (InAttribute) {
                    return XmlNodeType.Attribute;
                } else {
                    if (_virtualNodes.Count > 0) {
                        return _virtualNodes.Peek().nodeType;
                    } else {
                        return _tokenType;
                    }
                }
            }
        }

        public override bool Read()
        {
            if (_currentReadingFunction == null) {
                return false;
            } else if (_virtualNodes.Count > 0) {
                // TODO:
                _virtualNodes.Dequeue();
                return EndOfStream;
            } else {
                return _currentReadingFunction();
            }
        }

        private bool ReadBeforeDoctype()
        {
            while (ParseToken()) {
                switch (_tokenType) {
                case XmlNodeType.DocumentType:
                    if (!_doctypeInError) {
                        _currentReadingFunction = ReadBeforeRootElement;
                        return true;
                    }
                    break;
                case XmlNodeType.Whitespace:
                    return true;
                default:
                    // ignore
                    break;
                }
            }
            return false;
        }

        private bool ReadBeforeRootElement()
        {
            while (ParseToken()) {
                switch (_tokenType) {
                case XmlNodeType.DocumentType:
                    OnParseError("No more than one DOCTYPE per document");
                    break;
                case XmlNodeType.Comment:
                case XmlNodeType.Whitespace:
                    return true;
                case XmlNodeType.Text:
                case XmlNodeType.Element:
                case XmlNodeType.EndElement:
                    if (_tokenType != XmlNodeType.Element || NameTable.Add(_name) != NameTable.Add("html")) {
                        _virtualNodes.Enqueue(new Node(NameTable.Add("html")));
                    }
                    return true;
                default:
                    throw new NotSupportedException();
                }
            }
            return false;
        }

        private bool ReadBeforeHead()
        {
            while (ParseToken()) {
                switch (_tokenType) {
                case XmlNodeType.DocumentType:
                    OnParseError("No more than one DOCTYPE per document");
                    break;
                case XmlNodeType.Comment:
                case XmlNodeType.Whitespace:
                    return true;
                case XmlNodeType.Text:
                case XmlNodeType.Element:
                case XmlNodeType.EndElement:
                    if (_tokenType != XmlNodeType.Element || NameTable.Add(_name) != NameTable.Add("html")) {
                        _virtualNodes.Enqueue(new Node(NameTable.Add("html")));
                    }
                    return true;
                default:
                    throw new NotSupportedException();
                }
            }
            return false;
        }

        public override bool ReadAttributeValue()
        {
            throw new NotImplementedException();
        }

        public override ReadState ReadState
        {
            get {
                if (_disposed) {
                    return ReadState.Closed;
                } else if (EOF) {
                    return ReadState.EndOfFile;
                } else if (NodeType == XmlNodeType.None) {
                    return ReadState.Initial;
                } else if (_currentReadingFunction == null) {
                    return ReadState.Error;
                } else {
                    return ReadState.Interactive;
                }
            }
        }

        public override string Value
        {
            get
            {
                switch (NodeType) {
                case XmlNodeType.Attribute:
                    // TODO: handle "virtual nodes" generated by MoveToAttribute
                    throw new NotImplementedException();
                case XmlNodeType.CDATA:
                case XmlNodeType.Comment:
                case XmlNodeType.Text:
                case XmlNodeType.SignificantWhitespace:
                case XmlNodeType.Whitespace:
                    Debug.Assert(_virtualNodes.Count == 0);
                    return _value;
                case XmlNodeType.DocumentType:
                case XmlNodeType.ProcessingInstruction:
                case XmlNodeType.XmlDeclaration:
                    throw new InvalidOperationException();
                default:
                    return String.Empty;
                }
            }
        }

        private bool IsInScope(string target, bool tableScope)
        {
            int index = _openElements.FindLastIndex(delegate(string s) { return target.Equals(s, StringComparison.InvariantCultureIgnoreCase); });
            if (index >= 0) {
                if (!tableScope) {
                    return index >= _openElements.FindLastIndex(delegate(string s) { return Constants.IsScopingElement(s); });
                } else {
                    return true;
                }
            } else {
                return false;
            }
        }

        private void GenerateImpliedEndTags(string except)
        {
            //string[] ss = new string[] { "dd", "dt", "li", "p", "td", "th", "tr" };
            throw new NotImplementedException();
        }

        private void GenerateImpliedEndTags()
        {
            GenerateImpliedEndTags(null);
        }
    }
}
